package com.haidada.tk.utils;

import lombok.NonNull;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;

/**
 * @author Hai
 * @Description: 日期时间工具类
 */
public class DateUtils {
    private DateUtils() {

    }

    /**
     * 默认 zoneId
     */
    private static final ZoneId DEFAULT_ZONE_ID = ZoneId.systemDefault();
    private static final ZoneOffset DEFAULT_ZONE_OFFSET = DEFAULT_ZONE_ID.getRules().getOffset(Instant.now());

    /**
     * 时间格式（yyyy-MM-dd）
     */
    public static final String DATE_PATTERN = "yyyy-MM-dd";
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_PATTERN);

    /**
     * 时间格式（yyyy-MM-dd HH:mm:ss）
     */
    public static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);

    private static final Map<String, DateTimeFormatter> DATE_TIME_FORMATTER_MAP = new HashMap<>(2);

    static {
        DATE_TIME_FORMATTER_MAP.put(DATE_PATTERN, DATE_FORMATTER);
        DATE_TIME_FORMATTER_MAP.put(DATE_TIME_PATTERN, DATE_TIME_FORMATTER);
    }

    /**
     * 从全局缓存中拿 pattern 对应的 formatter 或者新建
     *
     * @param pattern pattern
     * @return pattern 对应的 formatter
     */
    private static DateTimeFormatter getFormatter(@NonNull String pattern) {
        return DATE_TIME_FORMATTER_MAP.getOrDefault(pattern, DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * 日期格式化为指定格式的字符串
     *
     * @param date    日期
     * @param pattern 格式，如：DateUtils.DATE_PATTERN
     * @return 返回指定格式字符串时间
     */
    public static String format(@NonNull Date date, @NonNull String pattern) {
        return formatLocalDateTime(toLocalDateTime(date), pattern);
    }

    /**
     * LocalDate 类型的日期格式化为指定格式的字符串
     *
     * @param localDate LocalDate 类型的日期
     * @param pattern   格式，如：DateUtils.DATE_PATTERN
     * @return 返回指定格式字符串时间
     */
    public static String formatLocalDate(@NonNull LocalDate localDate, @NonNull String pattern) {
        return formatLocalDateTime(toLocalDateTime(localDate), pattern);
    }

    /**
     * LocalDateTime 类型的时间格式化为指定格式的字符串
     *
     * @param localDateTime LocalDateTime 类型的时间
     * @param pattern       格式，如 DateUtils.DATE_PATTERN
     * @return 指定格式字符串时间
     */
    public static String formatLocalDateTime(@NonNull LocalDateTime localDateTime, @NonNull String pattern) {
        return localDateTime.format(getFormatter(pattern));
    }

    /**
     * 字符串转成 Date 类型
     *
     * @param str     日期字符串
     * @param pattern 日期的格式：如：DateUtils.DATE_PATTERN
     * @return Date 类型的时间
     */
    public static Date toDate(@NonNull String str, @NonNull String pattern) {
        return toDate(LocalDateTime.parse(str, getFormatter(pattern)));
    }

    /**
     * 字符串转成 Date 类型
     *
     * @param str 日期字符串
     * @return Date 类型的时间
     */
    public static Date toDate(@NonNull String str) {
        return toDate(LocalDateTime.parse(str, getFormatter(DateUtils.DATE_TIME_PATTERN)));
    }

    /**
     * LocalDate 转成 Date
     *
     * @param localDate LocalDate 类型日期
     * @return Date 类型的日期
     */
    public static Date toDate(@NonNull LocalDate localDate) {
        return Date.from(localDate.atStartOfDay(DEFAULT_ZONE_ID).toInstant());
    }

    /**
     * LocalDateTime 转成 Date
     *
     * @param localDateTime LocalDateTime 类型时间
     * @return Date 类型的时间
     */
    public static Date toDate(@NonNull LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(DEFAULT_ZONE_ID).toInstant());
    }

    /**
     * 字符串转成 LocalDate 类型的日期 默认 str 形如 "yyyy-MM-dd"
     *
     * @param str 字符串日期
     * @return LocalDate 类型的日期
     */
    public static LocalDate toLocalDate(@NonNull String str) {
        return toLocalDate(str, DATE_PATTERN);
    }

    /**
     * 字符串转成 LocalDate 类型的日期
     *
     * @param str     字符串日期
     * @param pattern 字符串格式，如 DateUtils.DATE_PATTERN
     * @return LocalDate 类型的日期
     */
    public static LocalDate toLocalDate(@NonNull String str, @NonNull String pattern) {
        return LocalDate.parse(str, getFormatter(pattern));
    }

    /**
     * Date 类型日期转成 LocalDate 类型的日期
     *
     * @param date Date 类型的日期
     * @return LocalDate 类型的日期
     */
    public static LocalDate toLocalDate(@NonNull Date date) {
        return toZonedDateTime(date).toLocalDate();
    }

    /**
     * 字符串类型的时间转成 LocalDateTime 类型的时间，默认形如 "yyyy-MM-dd HH:mm:ss"
     *
     * @param str 字符串时间，默认形如 "yyyy-MM-dd HH:mm:ss"
     * @return LocalDateTime 类型的时间
     */
    public static LocalDateTime toLocalDateTime(@NonNull String str) {
        return toLocalDateTime(str, DATE_TIME_PATTERN);
    }

    /**
     * 字符串类型的时间转成 LocalDateTime 类型的时间
     *
     * @param str     字符串时间
     * @param pattern 字符串时间格式
     * @return LocalDateTime 类型的时间
     */
    public static LocalDateTime toLocalDateTime(@NonNull String str, @NonNull String pattern) {
        return LocalDateTime.parse(str, getFormatter(pattern));
    }


    /**
     * Date 类型的时间转成 LocalDateTime 类型的时间
     *
     * @param date Date 类型的时间
     * @return LocalDateTime 类型的时间
     */
    public static LocalDateTime toLocalDateTime(@NonNull Date date) {
        return toZonedDateTime(date).toLocalDateTime();
    }

    /**
     * LocalDate 时间转成 LocalDateTime 类型时间为当天开始时间
     *
     * @param localDate LocalDate 类型的时间
     * @return LocalDateTime 类型时间为当天开始时间
     */
    public static LocalDateTime toLocalDateTime(@NonNull LocalDate localDate) {
        return localDate.atStartOfDay();
    }

    /**
     * 对日期的【秒】进行加/减
     *
     * @param date    日期
     * @param seconds 秒数，负数为减
     * @return 加/减几秒后的日期
     */
    public static Date addDateSeconds(@NonNull Date date, int seconds) {
        return toDate(toLocalDateTime(date).plusSeconds(seconds));
    }

    /**
     * 对日期的【分钟】进行加/减
     *
     * @param date    日期
     * @param minutes 分钟数，负数为减
     * @return 加/减几分钟后的日期
     */
    public static Date addDateMinutes(@NonNull Date date, int minutes) {
        return toDate(toLocalDateTime(date).plusMinutes(minutes));
    }

    /**
     * 对日期的【小时】进行加/减
     *
     * @param date  日期
     * @param hours 小时数，负数为减
     * @return 加/减几小时后的日期
     */
    public static Date addDateHours(@NonNull Date date, int hours) {
        return toDate(toLocalDateTime(date).plusHours(hours));
    }

    /**
     * 对日期的【天】进行加/减
     *
     * @param date 日期
     * @param days 天数，负数为减
     * @return 加/减几天后的日期
     */
    public static Date addDateDays(@NonNull Date date, int days) {
        return toDate(toLocalDateTime(date).plusDays(days));
    }

    /**
     * 对日期的【周】进行加/减
     *
     * @param date  日期
     * @param weeks 周数，负数为减
     * @return 加/减几周后的日期
     */
    public static Date addDateWeeks(@NonNull Date date, int weeks) {
        return toDate(toLocalDateTime(date).plusWeeks(weeks));
    }

    /**
     * 对日期的【月】进行加/减
     *
     * @param date   日期
     * @param months 月数，负数为减
     * @return 加/减几月后的日期
     */
    public static Date addDateMonths(@NonNull Date date, int months) {
        return toDate(toLocalDateTime(date).plusMonths(months));
    }

    /**
     * 对日期的【年】进行加/减
     *
     * @param date  日期
     * @param years 年数，负数为减
     * @return 加/减几年后的日期
     */
    public static Date addDateYears(@NonNull Date date, int years) {
        return toDate(toLocalDateTime(date).plusYears(years));
    }

    /**
     * 从 Date 获取特定时区的时间
     *
     * @param date Date 类型的时间
     * @return DateUtils.DEFAULT_ZONE_ID 标定的时区时间
     */
    public static ZonedDateTime toZonedDateTime(@NonNull Date date) {
        return date.toInstant().atZone(DEFAULT_ZONE_ID);
    }

    /**
     * 从秒数拿到 LocalDateTime
     *
     * @param seconds 秒数
     * @return localDateTime
     */
    public static LocalDateTime fromSeconds(long seconds) {
        return LocalDateTime.ofEpochSecond(seconds, 0, DEFAULT_ZONE_OFFSET);
    }

    /**
     * 从毫秒数拿到 LocalDateTime
     *
     * @param millSeconds 毫秒数
     * @return localDateTime
     */
    public static LocalDateTime fromMillSeconds(long millSeconds) {
        Instant instant = Instant.ofEpochMilli(millSeconds);
        return LocalDateTime.ofInstant(instant, DEFAULT_ZONE_ID);
    }

    /**
     * 从 LocalDateTime 拿到秒数
     *
     * @param localDateTime localDateTime
     * @return 秒数
     */
    public static long getSeconds(@NonNull LocalDateTime localDateTime) {
        return getMillSeconds(localDateTime) / 1000;
    }

    /**
     * 从 LocalDateTime 拿到毫秒数
     *
     * @param localDateTime localDateTime
     * @return 毫秒数
     */
    public static long getMillSeconds(@NonNull LocalDateTime localDateTime) {
        return localDateTime.toInstant(DEFAULT_ZONE_OFFSET).toEpochMilli();
    }

    /**
     * 当天剩余的秒数
     *
     * @return 当天剩余的秒数
     */
    public static long getDayRemainSeconds() {
        Date now = new Date();
        String str = format(addDateDays(now, 1), "yyyy-MM-dd");

        Date d = parse(str, "yyyy-MM-dd");
        return (d.getTime() - now.getTime()) / 1000;
    }

    public static Date parse(String str, String pattern) {
        try {
            return org.apache.commons.lang3.time.DateUtils.parseDate(str, "yyyy-MM-dd");
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 日期格式
     *
     * @return 日期格式
     */
    public static SimpleDateFormat sdf() {
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    }

    /**
     * 获取今天的开始时间
     */
    public static Date getDayBegin() {
        Calendar cal = new GregorianCalendar();
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }

    /**
     * 获取今天的结束时间
     */
    public static Date getDayEnd() {
        Calendar cal = new GregorianCalendar();
        cal.set(Calendar.HOUR_OF_DAY, 23);
        cal.set(Calendar.MINUTE, 59);
        cal.set(Calendar.SECOND, 59);
        return cal.getTime();
    }

    /**
     * 获取昨天的开始时间
     */
    public static Date getBeginDayOfYesterday() {
        Calendar cal = new GregorianCalendar();
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        cal.add(Calendar.DAY_OF_MONTH, -1);
        return cal.getTime();
    }

    /**
     * 获取昨天的结束时间
     */
    public static Date getEndDayOfYesterDay() {
        Calendar cal = new GregorianCalendar();
        cal.set(Calendar.HOUR_OF_DAY, 23);
        cal.set(Calendar.MINUTE, 59);
        cal.set(Calendar.SECOND, 59);
        cal.add(Calendar.DAY_OF_MONTH, -1);
        return cal.getTime();
    }

    /**
     * 获得本周一0点时间
     */
    public static Date getTimesWeekmorning() {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd 00:00:00");
        Calendar c = Calendar.getInstance();
        int day_of_week = c.get(Calendar.DAY_OF_WEEK) - 1;
        if (day_of_week == 0)
            day_of_week = 7;
        c.add(Calendar.DATE, -day_of_week + 1);
        return c.getTime();
    }

    /**
     * 获得本周日24点时间
     */
    public static Date getTimesWeeknight() {
        SimpleDateFormat formatDate = new SimpleDateFormat("yyyy-MM-dd 23:59:59");
        Calendar c = Calendar.getInstance();
        int day_of_week = c.get(Calendar.DAY_OF_WEEK) - 1;
        if (day_of_week == 0)
            day_of_week = 7;
        c.add(Calendar.DATE, -day_of_week + 7);
        return c.getTime();
    }

    /**
     * 根据当前日期获得最近n周的日期区间（不包含本周）
     */
    public static Date getFromToDate(SimpleDateFormat sdf, Date date, int n, int option, int k) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1;
        int offset = 0 == option ? 1 - dayOfWeek : 7 - dayOfWeek;
        int amount = 0 == option ? offset - (n - 1 + k) * 7 : offset - k * 7;
        calendar.add(Calendar.DATE, amount);
        return calendar.getTime();
    }

    /**
     * 获取上周的开始时间（不包含本周）
     */
    public static Date getBeginDayOfLastWeek() {
        //上周日期
        SimpleDateFormat sdf = sdf();
        Date beginDate = getFromToDate(sdf, new Date(), 1, 0, 1);

        Calendar calendar = Calendar.getInstance();
        try {
            calendar.setTime(beginDate);
        } catch (Exception e) {
            e.printStackTrace();
        }
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar.getTime();
    }

    /**
     * 获取上周的结束时间（不包含本周）
     */
    public static Date getEndDayOfLastWeek() {
        //上周日期
        SimpleDateFormat sdf = sdf();
        Date endDate = getFromToDate(sdf, new Date(), 1, 1, 1);

        Calendar calendar = Calendar.getInstance();
        try {
            calendar.setTime(endDate);
        } catch (Exception e) {
            e.printStackTrace();
        }
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        return calendar.getTime();
    }

    /**
     * 获得本月第一天0点时间
     */
    public static Date getTimesMonthmorning() {
        Calendar cal = Calendar.getInstance();
        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
        cal.set(Calendar.DAY_OF_MONTH, cal.getActualMinimum(Calendar.DAY_OF_MONTH));
        return cal.getTime();
    }

    /**
     * 获得本月最后一天24点时间
     */
    public static Date getTimesMonthnight() {
        Calendar cal = Calendar.getInstance();
        cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 23, 59, 59);
        cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
        cal.set(Calendar.HOUR_OF_DAY, 23);
        return cal.getTime();
    }

    /**
     * 获得上月第一天0点时间
     */
    public static Date getTimesLastMonthmorning() {
        //上月日期
        Calendar c = Calendar.getInstance();
        c.add(Calendar.MONTH, -1);
        SimpleDateFormat sdf = sdf();
        String gtimelast = sdf.format(c.getTime()); //上月
        int lastMonthMaxDay = c.getActualMaximum(Calendar.DAY_OF_MONTH);
        c.set(c.get(Calendar.YEAR), c.get(Calendar.MONTH), lastMonthMaxDay, 23, 59, 59);

        //按格式输出
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-01 00:00:00");
        return c.getTime();
    }

    /**
     * 获得上月最后一天24点时间
     */
    public static Date getTimesLastMonthnight() {
        //上月日期
        Calendar c = Calendar.getInstance();
        c.add(Calendar.MONTH, -1);
        SimpleDateFormat sdf = sdf();
        String gtimelast = sdf.format(c.getTime()); //上月
        int lastMonthMaxDay = c.getActualMaximum(Calendar.DAY_OF_MONTH);
        c.set(c.get(Calendar.YEAR), c.get(Calendar.MONTH), lastMonthMaxDay, 23, 59, 59);

        //按格式输出
        return c.getTime();
    }

    /**
     * 计算当前日期与{@code endDate}的间隔天数
     *
     * @param endDate 结束日期
     * @return 间隔天数
     */
    public static long until(LocalDate endDate) {
        return endDate.until(LocalDate.now(), ChronoUnit.DAYS);
    }

    /**
     * 获取到当前时间的间隔天数
     *
     * @param startDate 开始时间
     * @return 间隔天数
     */
    public static long differentDaysToNow(Date startDate) {
        Instant startInclusive=startDate.toInstant();
        Instant endExclusive=new Date().toInstant();
        return Duration.between(startInclusive, endExclusive).toDays();
    }

    /**
     * 获取多年之后的今天
     *
     * @param years 年数
     * @return 多少年后今天
     */
    public static Date addYearsFromNow(int years) {
        return addDateYears(new Date(), years);
    }

    /***
     * 返回今天的日期
     * @return 今天的日期
     */
    public static String today() {
        return format(new Date(), DATE_PATTERN);
    }

    /**
     * 获取到当前时间的间隔分钟
     *
     * @param startDate 开始时间
     * @return 到当前时间的间隔分钟
     */
    public static long diffMinutesToNow(Date startDate) {
        Instant startInclusive=startDate.toInstant();
        Instant endExclusive=new Date().toInstant();
        return Duration.between(startInclusive, endExclusive).toMinutes();
    }

    /**
     * 获取到当前时间的间隔分钟
     *
     * @param startDate 开始时间
     * @return 到当前时间的间隔分钟
     */
    public static long durationMinutes(Date startDate) {
        Instant startInclusive=startDate.toInstant();
        Instant endExclusive=new Date().toInstant();
        return Duration.between(startInclusive, endExclusive).toMinutes();
    }

    public static long differentSeconds(Date createdTime, Date payTime) {
        return payTime.getTime() - createdTime.getTime() / 1000;
    }


    /**
     * 由出生日期获得年龄
     */
    public static int getAge(Date birthDay) {
        Calendar cal = Calendar.getInstance();

        if (cal.before(birthDay)) {
            throw new IllegalArgumentException(
                    "The birthDay is before Now.It's unbelievable!");
        }
        int yearNow = cal.get(Calendar.YEAR);
        int monthNow = cal.get(Calendar.MONTH);
        int dayOfMonthNow = cal.get(Calendar.DAY_OF_MONTH);
        cal.setTime(birthDay);

        int yearBirth = cal.get(Calendar.YEAR);
        int monthBirth = cal.get(Calendar.MONTH);
        int dayOfMonthBirth = cal.get(Calendar.DAY_OF_MONTH);

        int age = yearNow - yearBirth;

        if (monthNow <= monthBirth) {
            if (monthNow == monthBirth) {
                if (dayOfMonthNow < dayOfMonthBirth) age--;
            } else {
                age--;
            }
        }
        return age;
    }

    /**
     * 获取特定时间的 毫秒数
     */
    public static Long getMiol() {
        Calendar ca = Calendar.getInstance();
        //失效的时间
        ca.set(Calendar.HOUR_OF_DAY, 23);
        ca.set(Calendar.MINUTE, 30);
        ca.set(Calendar.SECOND, 0);
        long fl = ca.getTimeInMillis();
        return fl;
    }


}
